﻿using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using LitJson;
using System;
using System.Reflection; 
using UnityEditor;

namespace Inlycat.AI.BehaviorTree
{

    public enum ActionResult
    {
        SUCCESS,
        RUNNING,
        FAILURE,
        NONE
    }

    /// <summary>
    /// BTree Node
    /// </summary>
    public class BNode
    {
        protected string m_StrType;
        protected string m_StrName = "node";

        protected BNode m_CParent;
        protected List<BNode> m_ListChildren = new List<BNode>();

        public BNode()
        {
            this.m_StrType = this.GetType().FullName;
            this.m_StrName = this.GetType().Name;
        }


        public void ReadJson(JsonData json)
        {
            this.m_StrType = json["type"].ToString();

            JsonData arg = json["arg"];
            Type t = this.GetType();
            FieldInfo[] fieldInfos = t.GetFields();
            for (int i = 0; i < fieldInfos.Length; i++)
            {
                FieldInfo info = fieldInfos[i];
                if (arg.Keys.Contains(info.Name))
                {
                    string str = arg[info.Name].ToString();
                    object val = null;
                    if (info.FieldType == typeof(int))
                    {
                        val = int.Parse(str);
                    }
                    else if (info.FieldType == typeof(float))
                    {
                        val = float.Parse(str);
                    }
                    else if (info.FieldType == typeof(bool))
                    {
                        val = bool.Parse(str);
                    }
                    else if (info.FieldType == typeof(string))
                    {
                        val = str;
                    }

                    info.SetValue(this, val);
                }
            }


            for (int i = 0; i < json["child"].Count; i++)
            {
                string typeName = json["child"][i]["type"].ToString();
                Type tt = Type.GetType(typeName);
                BNode eNode = Activator.CreateInstance(tt) as BNode;
                eNode.ReadJson(json["child"][i]);
                eNode.m_CParent = this;
                this.AddChild(eNode);
            }
        }

        public JsonData WriteJson()
        {
            JsonData json = new JsonData();
            json["type"] = this.m_StrType;
            json["name"] = this.m_StrName;

            json["arg"] = new JsonData();
            json["arg"].SetJsonType(JsonType.Object);
            Type t = this.GetType();
            FieldInfo[] fieldInfos = t.GetFields();
            for (int i = 0; i < fieldInfos.Length; i++)
            {
                FieldInfo info = fieldInfos[i];
                json["arg"][info.Name] = info.GetValue(this).ToString();
            }

            json["child"] = new JsonData();
            json["child"].SetJsonType(JsonType.Array);
            for (int i = 0; i < this.m_ListChildren.Count; i++)
            {
                JsonData child = this.m_ListChildren[i].WriteJson();
                json["child"].Add(child);
            }
            return json;
        }

        private ActionResult m_EState;

        public ActionResult RunNode(BInput input)
        {
            if (this.m_EState == ActionResult.NONE)
            {
                this.OnEnter(input);
                this.m_EState = ActionResult.RUNNING;
            }

            ActionResult res = this.Execute(input);
            if (res != ActionResult.RUNNING)
            {
                this.OnExit(input);
                this.m_EState = ActionResult.NONE;
            }
            return res;
        }

        #region virtual methods

        public virtual void OnEnter(BInput input)
        {

        }

        public virtual ActionResult Execute(BInput input)
        {
            return ActionResult.SUCCESS;
        }

        public virtual void OnExit(BInput input)
        {

        }


        #endregion virtual methods

        #region get or set methods

        public string GetTypeName()
        {
            return this.m_StrType;
        }

        public void SetTypeName(string type)
        {
            this.m_StrType = type;
        }

        public string GetName()
        {
            return this.m_StrName;
        }

        public void RemoveChild(BNode node)
        {
            this.m_ListChildren.Remove(node);
        }

        public void AddChild(BNode node)
        {
            this.m_ListChildren.Add(node);
        }

        public void InsertChild(BNode preNode, BNode node)
        {
            int index = this.m_ListChildren.FindIndex(a => a == preNode);
            this.m_ListChildren.Insert(index, node);
        }

        public void ReplaceChild(BNode preNode, BNode node)
        {
            int index = this.m_ListChildren.FindIndex(a => a == preNode);
            this.m_ListChildren[index] = node;
        }

        public bool ContainChild(BNode node)
        {
            return this.m_ListChildren.Contains(node);
        }

        #endregion get or set methods

#if UNITY_EDITOR
        public void RenderEditor(int x, int y)
        {
            try
            {
                Type t = this.GetType();
                FieldInfo[] fieldInfos = t.GetFields();
                for (int i = 0; i < fieldInfos.Length; i++)
                {
                    FieldInfo info = fieldInfos[i];
                    object val = null;
                    if (info.FieldType == typeof(int))
                    {
                        string fieldValue = info.GetValue(this).ToString();
                        GUI.Label(new Rect(x, y + i * 20, 100, 20), info.Name);
                        fieldValue = GUI.TextField(new Rect(x + 100, y + i * 20, 100, 20), fieldValue);
                        val = int.Parse(fieldValue);
                    }
                    else if (info.FieldType == typeof(float))
                    {
                        string fieldValue = info.GetValue(this).ToString();
                        GUI.Label(new Rect(x, y + i * 20, 100, 20), info.Name);
                        fieldValue = GUI.TextField(new Rect(x + 100, y + i * 20, 100, 20), fieldValue);
                        val = float.Parse(fieldValue);
                    }
                    else if (info.FieldType == typeof(bool))
                    {
                        bool fieldValue = (bool)info.GetValue(this);
                        GUI.Label(new Rect(x, y + i * 20, 100, 20), info.Name);
                        fieldValue = GUI.Toggle(new Rect(x + 100, y + i * 20, 100, 20), fieldValue, "");
                        val = fieldValue;
                    }
                    else if (info.FieldType == typeof(string))
                    {
                        string fieldValue = info.GetValue(this).ToString();
                        GUI.Label(new Rect(x, y + i * 20, 100, 20), info.Name);
                        fieldValue = GUI.TextField(new Rect(x + 100, y + i * 20, 100, 20), fieldValue);
                        val = fieldValue;
                    }

                    info.SetValue(this, val);
                }
            }
            catch (Exception)
            {

                throw;
            }
        }


        private void Menu_Add_Callback(object arg)
        {
            Type t = arg as Type;
            BNode node = Activator.CreateInstance(t) as BNode;

            this.AddChild(node);
            node.m_CParent = this;
            BTreeWin.Instance.Repaint();
        }

        private void Menu_Switch_Callback(object arg)
        {
            Type t = arg as Type;
            BNode node = Activator.CreateInstance(t) as BNode;
            node.m_CParent = this.m_CParent;

            foreach (var mListChild in this.m_ListChildren)
            {
                node.AddChild(mListChild);
            }

            if (this.m_CParent != null)
            {
                this.m_CParent.ReplaceChild(this, node);
            }
            else if (BTreeWin.CurrentTree.Root == this)
            {
                BTreeWin.CurrentTree.Root = node;
            }

            BTreeWin.CurrentNode = node;
            BTreeWin.Instance.Repaint();
        }

        private void Menu_Delete_Node(object arg)
        {
            if (this.m_CParent != null)
            {
                this.m_CParent.RemoveChild(this);
            }

            this.m_CParent = null;
            BTreeWin.Select = null;
            BTreeWin.CurrentNode = null;
            BTreeWin.Instance.Repaint();
        }

        public virtual void Render(int x, ref int y)
        {
            Event evt = Event.current;
            if (BTreeWin.CurrentNode == this)
            {
                Texture2D texRed = new Texture2D(1, 1);
                texRed.SetPixel(0, 0, Color.blue);
                texRed.Apply();
                GUI.DrawTexture(new Rect(0, y, BTreeWin.Instance.position.width, BTreeWin.NODE_HEIGHT), texRed);
            }

            Rect moveRect = new Rect(x, y, BTreeWin.Instance.position.width - BTreeWin.GUI_WIDTH, 5);
            bool is_move_node = false;
            if (BTreeWin.Select != null && moveRect.Contains(evt.mousePosition))
            {
                is_move_node = true;
                Texture2D tex = new Texture2D(1, 1);
                tex.SetPixel(0, 0, Color.green);
                tex.Apply();
                GUI.DrawTexture(new Rect(x, y, BTreeWin.Instance.position.width, 2), tex);
                if (evt.button == 0 && evt.type == EventType.MouseUp)
                {
                    if (this != BTreeWin.Select && this.m_CParent != null)
                    {
                        BTreeWin.Select.m_CParent.RemoveChild(BTreeWin.Select);
                        BTreeWin.Select.m_CParent = this.m_CParent;
                        this.m_CParent.InsertChild(this, BTreeWin.Select);
                    }
                    BTreeWin.Select = null;
                    BTreeWin.Instance.Repaint();
                }
            }

            Rect rect = new Rect(x, y, BTreeWin.Instance.position.width - BTreeWin.GUI_WIDTH, BTreeWin.NODE_HEIGHT);
            if (!is_move_node && rect.Contains(evt.mousePosition))
            {
                if (BTreeWin.Select != null)
                {
                    Texture2D texRed = new Texture2D(1, 1);
                    texRed.SetPixel(0, 0, Color.red);
                    texRed.Apply();
                    GUI.DrawTexture(new Rect(0, y, BTreeWin.Instance.position.width, BTreeWin.NODE_HEIGHT), texRed);
                }

                if (evt.type == EventType.ContextClick)
                {
                    GenericMenu menu = new GenericMenu();
                    foreach (Type item in BNodeFactory.Instance.m_ListComposite)
                    {
                        menu.AddItem(new GUIContent("Create/Composite/" + item.Name), false, Menu_Add_Callback, item);
                    }

                    foreach (Type item in BNodeFactory.Instance.m_ListAction)
                    {
                        menu.AddItem(new GUIContent("Create/Action/" + item.Name), false, Menu_Add_Callback, item);
                    }

                    foreach (Type item in BNodeFactory.Instance.m_ListCondition)
                    {
                        menu.AddItem(new GUIContent("Create/Condition/" + item.Name), false, Menu_Add_Callback, item);
                    }

                    foreach (Type item in BNodeFactory.Instance.m_ListDecorator)
                    {
                        menu.AddItem(new GUIContent("Create/Decorator/" + item.Name), false, Menu_Add_Callback, item);
                    }

                    foreach (Type item in BNodeFactory.Instance.m_ListComposite)
                    {
                        menu.AddItem(new GUIContent("Switch/Composite/" + item.Name), false, Menu_Switch_Callback, item);
                    }

                    menu.AddItem(new GUIContent("Delete"), false, Menu_Delete_Node, "");
                    menu.ShowAsContext();
                }

                if (evt.button == 0 && evt.type == EventType.MouseDown && this != BTreeWin.CurrentTree.Root)
                {
                    BTreeWin.Select = this;
                    BTreeWin.CurrentNode = this;
                }

                if (evt.button == 0 && evt.type == EventType.MouseUp && BTreeWin.Select != null)
                {
                    if (this != BTreeWin.Select)
                    {
                        BTreeWin.Select.m_CParent.RemoveChild(BTreeWin.Select);
                        BTreeWin.Select.m_CParent = this;
                        this.AddChild(BTreeWin.Select);
                    }
                    BTreeWin.Select = null;
                    BTreeWin.Instance.Repaint();
                }
            }

            GUI.Label(new Rect(x, y, BTreeWin.Instance.position.width, BTreeWin.NODE_HEIGHT), this.m_StrName);


            /************************* line ****************************/
            Vector3 pos1 = new Vector3(x + BTreeWin.NODE_WIDTH / 2, y + BTreeWin.NODE_HEIGHT / 2, 0);
            Handles.color = Color.cyan;

            for (int i = 0; i < this.m_ListChildren.Count; i++)
            {
                y = y + BTreeWin.NODE_HEIGHT;

                Vector3 pos2 = new Vector3(x + BTreeWin.NODE_WIDTH / 2, y + BTreeWin.NODE_HEIGHT / 2, 0);
                Vector3 pos3 = new Vector3(x + BTreeWin.NODE_WIDTH, y + BTreeWin.NODE_HEIGHT / 2, 0);
                this.m_ListChildren[i].Render(x + BTreeWin.NODE_WIDTH, ref y);
                Handles.DrawPolyLine(new[] { pos1, pos2, pos3 });
            }

        }


#endif
    }

}
